#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>

#include "mount_tree.h"

#include "dynamic_array.h"

#define ROOT_PATH "/"

/*********
 * Types *
 *********/

typedef struct mount_tree {
    char*                   name;
    char*                   full_path;
    char*                   filesystem;
    curious_dynamic_array_t children;
} mount_tree_t;

/**********************
 * Internal Functions *
 **********************/
static void          create_mount_tree(mount_tree_t* self, char* name, char* full_path, char* filesystem);
static void          destroy_mount_tree(mount_tree_t* self);
static mount_tree_t* get_mount(char* full_path, char** name);
static void          add_mount(char* full_path, char* filesystem);
static int           compare_mount_tree_by_name(char* name, mount_tree_t* tree);

static mount_tree_t root;

void curious_init_mount_tree(void)
{
    create_mount_tree(&root, ROOT_PATH, ROOT_PATH, "");

    FILE* mount_file = fopen("/proc/mounts", "r");

    ssize_t bytes_read;
    char*   line = NULL;
    size_t  size = 0;
#define VAL_SEP " "
    // Read every line in the list of mounts
    while (0 < (bytes_read = getline(&line, &size, mount_file))) {
        // Each line of /proc/mounts is a list of space-seperated values
        // We don't care about the first value, so just skip it
        strtok(line, VAL_SEP);

        // The second element is the full path to the mount
        char* full_path = strtok(NULL, VAL_SEP);

        // The third element is the type of filesystem the mount uses
        char* filesystem = strtok(NULL, VAL_SEP);

        add_mount(full_path, filesystem);
    }

    free(line);
    fclose(mount_file);
}

void curious_finalize_mount_tree(void)
{
    destroy_mount_tree(&root);
}

void create_mount_tree(mount_tree_t* self, char* name, char* full_path, char* filesystem)
{
    //printf("registering %s mount at %s\n", filesystem, full_path);

    self->name       = strdup(name);
    self->full_path  = strdup(full_path);
    self->filesystem = strdup(filesystem);
    create_curious_dynamic_array(&self->children, 0, sizeof(mount_tree_t));
}

void destroy_mount_tree(mount_tree_t* self)
{
    free(self->name);
    free(self->full_path);
    free(self->filesystem);
    destroy_curious_dynamic_array(&self->children, (free_f) destroy_mount_tree);
}

mount_tree_t* get_mount(char* full_path, char** name)
{
    if (0 == strcmp(full_path, ROOT_PATH)) {
        return &root;
    }

    mount_tree_t* cur_mount = &root;
    mount_tree_t* next_mount;
#define DIR_SEP "/"
    // Look at every path elemet (i.e. each level of directories) in the full path...
    for (char* path_el = strtok(full_path, DIR_SEP); NULL != path_el; path_el = strtok(NULL, DIR_SEP)) {
        // The name should always be the last path element we've looked at
        if (NULL != name) {
            *name = path_el;
        }

        // ...see if there's a mount one level down which matches the current path element
        next_mount =
            find_in_curious_dynamic_array(&cur_mount->children, path_el, (compare_f) compare_mount_tree_by_name);
        // ...if we couldn't find a match, we've found the deepest relevant node,
        // meaning that we're done
        if (NULL == next_mount) {
            return cur_mount;

            // ...otherwise, we want to look at the childreen of the matching mount
        } else {
            cur_mount = next_mount;
        }
    }

    return &root;
}

void add_mount(char* full_path, char* filesystem)
{
    // If the path in question is to the root, no need to go looking;
    // we should just set its filesystem now
    if (0 == strcmp(full_path, ROOT_PATH)) {
        // make sure we don't overwrite that first setup
        // (the actual root entry is proper the first)
        if (0 != strcmp(root.filesystem, "")) {
            free(root.filesystem);
            root.filesystem = strdup(filesystem);
        }

        return;
    }

    char*         name         = NULL;
    char*         path_copy    = strdup(full_path);
    mount_tree_t* parent_mount = get_mount(path_copy, &name);

    if (NULL == parent_mount) {
        printf("No relevant mount found for %s\n", full_path);
    } else if (0 != strcmp(name, parent_mount->name)) {
        // Make a new child mount of the parent we found
        mount_tree_t mount;
        create_mount_tree(&mount, name, full_path, filesystem);
        append_to_curious_dynamic_array(&parent_mount->children, &mount);
    }

    // wait until after the mount tree nodehas been created to free this copy,
    // since name is part of that buffer
    free(path_copy);
}

void curious_get_filesystem(const char* path, char** filesystem, char** mount_point)
{
    // get_mount uses strtok, which mangeles its input string,
    // so we only want to pass in a copy
    char* path_copy;

    // if the path starts from the root, it's absolute already, so just copy it
    if ('/' == path[0]) {
        path_copy = strdup(path);

        // otherwise, we need to atually find the absolute path
        // and if we encounter an error, just set
    } else {
        path_copy = realpath(path, NULL);

        // if we can't resolve the absolute path, we can't find the proper mount
        if (NULL == path_copy) {
            *filesystem  = NULL;
            *mount_point = NULL;
            return;
        }
    }

    // Just find the deepest mount in the given path
    // and return which filesystem it uses
    mount_tree_t* mount = get_mount(path_copy, NULL);

    // strdup and realpath both allocate a string, so we need to free it
    free(path_copy);

    *filesystem  = mount->filesystem;
    *mount_point = mount->full_path;
}

int compare_mount_tree_by_name(char* name, mount_tree_t* tree)
{
    return strcmp(name, tree->name);
}
